package com.fausto.learnimprint.aspect

import com.fasterxml.jackson.databind.ObjectMapper
import com.fausto.learnimprint.cache.RedisCache
import com.fausto.learnimprint.common.exception.CacheNotFoundException
import com.fausto.learnimprint.log.Slf4j
import com.fausto.learnimprint.log.Slf4j.Companion.log
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.reflect.MethodSignature
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.stereotype.Component
import java.util.*
import java.util.concurrent.TimeUnit

@Slf4j
@Component
@Aspect
class RedisCacheAop {
    @Autowired
    lateinit var redisTemplate: RedisTemplate<String, String>

    @Autowired
    lateinit var objectMapper: ObjectMapper

    /**
     * AOP缓存实现的业务策略
     * 1：切入点表达式应该拦截 @RedisCache注解
     * 2：通知方法： 环绕通知
     * 注意：
     * 如果使用环绕通知，这必须在第一个参数的位置添加ProceedingJoinPoint
     *     完成redis配置  key
     *
     * 动态获取注解参数的步骤
     *     1.@annotation(cacheFind)   切入点表达式要求拦截一个类型为RedisCache注解。
     *     2.并且利用连接点为参数中的redisCache赋值
     */
    @Around("@annotation(redisCache)")  //切入点表达式(匹配所有标注有@RedisCache注解的内容)
    fun around(joinPoint: ProceedingJoinPoint, redisCache: RedisCache): Any {
        val result: Any
        try {
            //动态获取注解中的数据
            val prefixKey = redisCache.key
            //动态获取方法名
            val methodName = joinPoint.signature.name
            //动态获取方法中的参数
            val args = when(joinPoint.args.size) {
                //无参数
                0 -> ""
                //有参数
                else -> {
                    Arrays.toString(joinPoint.args).let {
                        it.substring(1, it.length-1)
                    }
                }
            }
            //拼接redis_key
            val redisKey = "$prefixKey::$methodName($args)"

            //校验redis中是否有数据
            if (redisTemplate.hasKey(redisKey)) {
                //有缓存, 从redis缓存中获取json, 还原成对象返回
                val json = redisTemplate.opsForValue().get(redisKey) ?: throw CacheNotFoundException()
                //动态获取目标方法的返回值类型
                val signature = joinPoint.signature as MethodSignature
                val returnType = signature.returnType
                //将json转换为对象
                result = objectMapper.readValue(json, returnType)
                log.info("[AOP获取缓存] $redisKey")
            } else {
                //无缓存,从数据库中获取
                result = joinPoint.proceed()
                //将结果转换为json并保存到redis
                val json = objectMapper.writeValueAsString(result)
                if (redisCache.expired > 0) {
                    //保存到redis并设置过期时间
                    redisTemplate.opsForValue().set(redisKey, json, redisCache.expired, TimeUnit.MINUTES)
                } else {
                    //保存到redis
                    redisTemplate.opsForValue().set(redisKey, json)
                }
                log.info("[AOP新增缓存] $redisKey")
            }
            //返回结果
            return result
        } catch (t: Throwable) {
            t.printStackTrace()
            throw RuntimeException(t)
        }
    }
}
